// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/gfx/animation/animation_container.h"

#include "ui/gfx/animation/animation_container_element.h"
#include "ui/gfx/animation/animation_container_observer.h"

using base::TimeDelta;
using base::TimeTicks;

namespace gfx {

AnimationContainer::AnimationContainer()
    : last_tick_time_(base::TimeTicks::Now())
    , observer_(NULL)
{
}

AnimationContainer::~AnimationContainer()
{
    // The animations own us and stop themselves before being deleted. If
    // elements_ is not empty, something is wrong.
    DCHECK(elements_.empty());
}

void AnimationContainer::Start(AnimationContainerElement* element)
{
    DCHECK(elements_.count(element) == 0); // Start should only be invoked if the
        // element isn't running.

    if (elements_.empty()) {
        last_tick_time_ = base::TimeTicks::Now();
        SetMinTimerInterval(element->GetTimerInterval());
    } else if (element->GetTimerInterval() < min_timer_interval_) {
        SetMinTimerInterval(element->GetTimerInterval());
    }

    element->SetStartTime(last_tick_time_);
    elements_.insert(element);
}

void AnimationContainer::Stop(AnimationContainerElement* element)
{
    DCHECK(elements_.count(element) > 0); // The element must be running.

    elements_.erase(element);

    if (elements_.empty()) {
        timer_.Stop();
        if (observer_)
            observer_->AnimationContainerEmpty(this);
    } else {
        TimeDelta min_timer_interval = GetMinInterval();
        if (min_timer_interval > min_timer_interval_)
            SetMinTimerInterval(min_timer_interval);
    }
}

void AnimationContainer::Run()
{
    // We notify the observer after updating all the elements. If all the elements
    // are deleted as a result of updating then our ref count would go to zero and
    // we would be deleted before we notify our observer. We add a reference to
    // ourself here to make sure we're still valid after running all the elements.
    scoped_refptr<AnimationContainer> this_ref(this);

    TimeTicks current_time = base::TimeTicks::Now();

    last_tick_time_ = current_time;

    // Make a copy of the elements to iterate over so that if any elements are
    // removed as part of invoking Step there aren't any problems.
    Elements elements = elements_;

    for (Elements::const_iterator i = elements.begin();
         i != elements.end(); ++i) {
        // Make sure the element is still valid.
        if (elements_.find(*i) != elements_.end())
            (*i)->Step(current_time);
    }

    if (observer_)
        observer_->AnimationContainerProgressed(this);
}

void AnimationContainer::SetMinTimerInterval(base::TimeDelta delta)
{
    // This doesn't take into account how far along the current element is, but
    // that shouldn't be a problem for uses of Animation/AnimationContainer.
    timer_.Stop();
    min_timer_interval_ = delta;
    timer_.Start(FROM_HERE, min_timer_interval_, this, &AnimationContainer::Run);
}

TimeDelta AnimationContainer::GetMinInterval()
{
    DCHECK(!elements_.empty());

    TimeDelta min;
    Elements::const_iterator i = elements_.begin();
    min = (*i)->GetTimerInterval();
    for (++i; i != elements_.end(); ++i) {
        if ((*i)->GetTimerInterval() < min)
            min = (*i)->GetTimerInterval();
    }
    return min;
}

} // namespace gfx
